{
 "cells": [
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 4.3",
   "id": "efc6a0d4d743fb3c"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-04-21T08:11:35.684324Z",
     "start_time": "2025-04-21T08:11:35.170918Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.preprocessing import LabelEncoder\n",
    "import pandas as pd\n",
    "from sklearn.tree import DecisionTreeClassifier\n",
    "from sklearn.tree import export_graphviz\n",
    "from IPython.display import Image\n",
    "import graphviz\n",
    "\n",
    "# 按照图中数据构建数据集\n",
    "data = {\n",
    "    '色泽': ['青绿', '乌黑', '浅白', '青绿', '浅白', '乌黑', '青绿', '浅白', '乌黑', '浅白', '青绿', '浅白', '乌黑', '浅白', '青绿'],\n",
    "    '根蒂': ['蜷缩', '蜷缩', '蜷缩', '稍蜷', '稍蜷', '稍蜷', '硬挺', '硬挺', '稍蜷', '稍蜷', '稍蜷', '稍蜷', '稍蜷', '蜷缩', '蜷缩'],\n",
    "    '敲声': ['沉闷', '沉闷', '沉闷', '沉闷', '清脆', '沉闷', '沉闷', '沉闷', '沉闷', '沉闷', '沉闷', '沉闷', '沉闷', '沉闷', '沉闷'],\n",
    "    '纹理': ['清晰', '清晰', '清晰', '清晰', '清晰', '稍糊', '稍糊', '模糊', '稍糊', '稍糊', '模糊', '模糊', '稍糊', '清晰', '清晰'],\n",
    "    '脐部': ['凹陷', '凹陷', '凹陷', '稍凹', '稍凹', '稍凹', '平坦', '平坦', '平坦', '稍凹', '稍凹', '稍凹', '凹陷', '凹陷', '凹陷'],\n",
    "    '触感': ['硬滑', '硬滑', '硬滑', '软粘', '软粘', '软粘', '软粘', '硬滑', '软粘', '软粘', '软粘', '硬滑', '软粘', '硬滑', '硬滑'],\n",
    "    '密度': [0.697, 0.774, 0.634, 0.608, 0.556, 0.403, 0.481, 0.437, 0.666, 0.243, 0.245, 0.343, 0.639, 0.657, 0.360],\n",
    "    '含糖率': [0.460, 0.376, 0.264, 0.318, 0.215, 0.237, 0.149, 0.211, 0.091, 0.057, 0.099, 0.161, 0.198, 0.370, 0.042],\n",
    "    '好瓜': ['是', '是', '是', '是', '是', '是', '否', '否', '否', '否', '否', '否', '否', '否', '否']\n",
    "}\n",
    "df = pd.DataFrame(data)\n",
    "\n",
    "# 对离散特征进行标签编码\n",
    "label_encoders = {}\n",
    "for col in df.columns[:-1]:\n",
    "    if df[col].dtype == 'object':\n",
    "        le = LabelEncoder()\n",
    "        df[col] = le.fit_transform(df[col])\n",
    "        label_encoders[col] = le\n",
    "\n",
    "# 特征和标签\n",
    "X = df.drop('好瓜', axis=1)\n",
    "y = df['好瓜']\n",
    "\n",
    "# 创建决策树分类器对象，这里使用信息熵（entropy）作为划分标准\n",
    "clf = DecisionTreeClassifier(criterion='entropy')\n",
    "# 训练模型\n",
    "clf.fit(X, y)\n",
    "\n",
    "# 生成决策树的dot文件\n",
    "dot_data = export_graphviz(clf, out_file=None,\n",
    "                           feature_names=X.columns,\n",
    "                           class_names=['否', '是'],\n",
    "                           filled=True, rounded=True,\n",
    "                           special_characters=True)\n",
    "# 可视化决策树\n",
    "graph = graphviz.Source(dot_data)\n",
    "graph"
   ],
   "id": "349ffe32f8d2eb9",
   "outputs": [
    {
     "data": {
      "image/svg+xml": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n<!-- Generated by graphviz version 12.2.1 (20241206.2353)\n -->\n<!-- Title: Tree Pages: 1 -->\n<svg width=\"343pt\" height=\"447pt\"\n viewBox=\"0.00 0.00 342.50 447.25\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 443.25)\">\n<title>Tree</title>\n<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-443.25 338.5,-443.25 338.5,4 -4,4\"/>\n<!-- 0 -->\n<g id=\"node1\" class=\"node\">\n<title>0</title>\n<path fill=\"#f6d5bd\" stroke=\"black\" d=\"M152.75,-439.25C152.75,-439.25 64.75,-439.25 64.75,-439.25 58.75,-439.25 52.75,-433.25 52.75,-427.25 52.75,-427.25 52.75,-364.5 52.75,-364.5 52.75,-358.5 58.75,-352.5 64.75,-352.5 64.75,-352.5 152.75,-352.5 152.75,-352.5 158.75,-352.5 164.75,-358.5 164.75,-364.5 164.75,-364.5 164.75,-427.25 164.75,-427.25 164.75,-433.25 158.75,-439.25 152.75,-439.25\"/>\n<text text-anchor=\"start\" x=\"60.75\" y=\"-421.95\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">含糖率 ≤ 0.213</text>\n<text text-anchor=\"start\" x=\"61.5\" y=\"-406.2\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">entropy = 0.971</text>\n<text text-anchor=\"start\" x=\"67.88\" y=\"-390.45\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">samples = 15</text>\n<text text-anchor=\"start\" x=\"69.75\" y=\"-374.7\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">value = [9, 6]</text>\n<text text-anchor=\"start\" x=\"77.62\" y=\"-358.95\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">class = 否</text>\n</g>\n<!-- 1 -->\n<g id=\"node2\" class=\"node\">\n<title>1</title>\n<path fill=\"#e58139\" stroke=\"black\" d=\"M83.5,-308.62C83.5,-308.62 12,-308.62 12,-308.62 6,-308.62 0,-302.62 0,-296.62 0,-296.62 0,-249.62 0,-249.62 0,-243.62 6,-237.62 12,-237.62 12,-237.62 83.5,-237.62 83.5,-237.62 89.5,-237.62 95.5,-243.62 95.5,-249.62 95.5,-249.62 95.5,-296.62 95.5,-296.62 95.5,-302.62 89.5,-308.62 83.5,-308.62\"/>\n<text text-anchor=\"start\" x=\"8\" y=\"-291.32\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">entropy = 0.0</text>\n<text text-anchor=\"start\" x=\"10.62\" y=\"-275.57\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">samples = 8</text>\n<text text-anchor=\"start\" x=\"8.75\" y=\"-259.82\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">value = [8, 0]</text>\n<text text-anchor=\"start\" x=\"16.62\" y=\"-244.07\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">class = 否</text>\n</g>\n<!-- 0&#45;&gt;1 -->\n<g id=\"edge1\" class=\"edge\">\n<title>0&#45;&gt;1</title>\n<path fill=\"none\" stroke=\"black\" d=\"M87.24,-352.3C81.78,-341.5 75.9,-329.86 70.39,-318.94\"/>\n<polygon fill=\"black\" stroke=\"black\" points=\"73.56,-317.45 65.93,-310.1 67.31,-320.61 73.56,-317.45\"/>\n<text text-anchor=\"middle\" x=\"57.44\" y=\"-327.08\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">True</text>\n</g>\n<!-- 2 -->\n<g id=\"node3\" class=\"node\">\n<title>2</title>\n<path fill=\"#5aade9\" stroke=\"black\" d=\"M213.75,-316.5C213.75,-316.5 125.75,-316.5 125.75,-316.5 119.75,-316.5 113.75,-310.5 113.75,-304.5 113.75,-304.5 113.75,-241.75 113.75,-241.75 113.75,-235.75 119.75,-229.75 125.75,-229.75 125.75,-229.75 213.75,-229.75 213.75,-229.75 219.75,-229.75 225.75,-235.75 225.75,-241.75 225.75,-241.75 225.75,-304.5 225.75,-304.5 225.75,-310.5 219.75,-316.5 213.75,-316.5\"/>\n<text text-anchor=\"start\" x=\"121.75\" y=\"-299.2\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">含糖率 ≤ 0.344</text>\n<text text-anchor=\"start\" x=\"122.5\" y=\"-283.45\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">entropy = 0.592</text>\n<text text-anchor=\"start\" x=\"132.62\" y=\"-267.7\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">samples = 7</text>\n<text text-anchor=\"start\" x=\"130.75\" y=\"-251.95\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">value = [1, 6]</text>\n<text text-anchor=\"start\" x=\"138.62\" y=\"-236.2\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">class = 是</text>\n</g>\n<!-- 0&#45;&gt;2 -->\n<g id=\"edge2\" class=\"edge\">\n<title>0&#45;&gt;2</title>\n<path fill=\"none\" stroke=\"black\" d=\"M130.26,-352.3C134.4,-344.1 138.79,-335.41 143.08,-326.92\"/>\n<polygon fill=\"black\" stroke=\"black\" points=\"146.16,-328.57 147.55,-318.07 139.92,-325.42 146.16,-328.57\"/>\n<text text-anchor=\"middle\" x=\"156.04\" y=\"-335.04\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">False</text>\n</g>\n<!-- 3 -->\n<g id=\"node4\" class=\"node\">\n<title>3</title>\n<path fill=\"#399de5\" stroke=\"black\" d=\"M144.5,-185.88C144.5,-185.88 73,-185.88 73,-185.88 67,-185.88 61,-179.88 61,-173.88 61,-173.88 61,-126.88 61,-126.88 61,-120.88 67,-114.88 73,-114.88 73,-114.88 144.5,-114.88 144.5,-114.88 150.5,-114.88 156.5,-120.88 156.5,-126.88 156.5,-126.88 156.5,-173.88 156.5,-173.88 156.5,-179.88 150.5,-185.88 144.5,-185.88\"/>\n<text text-anchor=\"start\" x=\"69\" y=\"-168.57\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">entropy = 0.0</text>\n<text text-anchor=\"start\" x=\"71.62\" y=\"-152.82\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">samples = 4</text>\n<text text-anchor=\"start\" x=\"69.75\" y=\"-137.07\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">value = [0, 4]</text>\n<text text-anchor=\"start\" x=\"77.62\" y=\"-121.33\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">class = 是</text>\n</g>\n<!-- 2&#45;&gt;3 -->\n<g id=\"edge3\" class=\"edge\">\n<title>2&#45;&gt;3</title>\n<path fill=\"none\" stroke=\"black\" d=\"M148.24,-229.55C142.78,-218.75 136.9,-207.11 131.39,-196.19\"/>\n<polygon fill=\"black\" stroke=\"black\" points=\"134.56,-194.7 126.93,-187.35 128.31,-197.86 134.56,-194.7\"/>\n</g>\n<!-- 4 -->\n<g id=\"node5\" class=\"node\">\n<title>4</title>\n<path fill=\"#9ccef2\" stroke=\"black\" d=\"M273,-193.75C273,-193.75 186.5,-193.75 186.5,-193.75 180.5,-193.75 174.5,-187.75 174.5,-181.75 174.5,-181.75 174.5,-119 174.5,-119 174.5,-113 180.5,-107 186.5,-107 186.5,-107 273,-107 273,-107 279,-107 285,-113 285,-119 285,-119 285,-181.75 285,-181.75 285,-187.75 279,-193.75 273,-193.75\"/>\n<text text-anchor=\"start\" x=\"189.62\" y=\"-176.45\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">密度 ≤ 0.677</text>\n<text text-anchor=\"start\" x=\"182.5\" y=\"-160.7\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">entropy = 0.918</text>\n<text text-anchor=\"start\" x=\"192.62\" y=\"-144.95\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">samples = 3</text>\n<text text-anchor=\"start\" x=\"190.75\" y=\"-129.2\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">value = [1, 2]</text>\n<text text-anchor=\"start\" x=\"198.62\" y=\"-113.45\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">class = 是</text>\n</g>\n<!-- 2&#45;&gt;4 -->\n<g id=\"edge4\" class=\"edge\">\n<title>2&#45;&gt;4</title>\n<path fill=\"none\" stroke=\"black\" d=\"M190.9,-229.55C194.98,-221.35 199.3,-212.66 203.51,-204.17\"/>\n<polygon fill=\"black\" stroke=\"black\" points=\"206.6,-205.84 207.91,-195.32 200.33,-202.72 206.6,-205.84\"/>\n</g>\n<!-- 5 -->\n<g id=\"node6\" class=\"node\">\n<title>5</title>\n<path fill=\"#e58139\" stroke=\"black\" d=\"M208.5,-71C208.5,-71 137,-71 137,-71 131,-71 125,-65 125,-59 125,-59 125,-12 125,-12 125,-6 131,0 137,0 137,0 208.5,0 208.5,0 214.5,0 220.5,-6 220.5,-12 220.5,-12 220.5,-59 220.5,-59 220.5,-65 214.5,-71 208.5,-71\"/>\n<text text-anchor=\"start\" x=\"133\" y=\"-53.7\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">entropy = 0.0</text>\n<text text-anchor=\"start\" x=\"135.62\" y=\"-37.95\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">samples = 1</text>\n<text text-anchor=\"start\" x=\"133.75\" y=\"-22.2\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">value = [1, 0]</text>\n<text text-anchor=\"start\" x=\"141.62\" y=\"-6.45\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">class = 否</text>\n</g>\n<!-- 4&#45;&gt;5 -->\n<g id=\"edge5\" class=\"edge\">\n<title>4&#45;&gt;5</title>\n<path fill=\"none\" stroke=\"black\" d=\"M208.2,-106.7C203.99,-98.36 199.56,-89.59 195.31,-81.18\"/>\n<polygon fill=\"black\" stroke=\"black\" points=\"198.57,-79.86 190.93,-72.51 192.32,-83.01 198.57,-79.86\"/>\n</g>\n<!-- 6 -->\n<g id=\"node7\" class=\"node\">\n<title>6</title>\n<path fill=\"#399de5\" stroke=\"black\" d=\"M322.5,-71C322.5,-71 251,-71 251,-71 245,-71 239,-65 239,-59 239,-59 239,-12 239,-12 239,-6 245,0 251,0 251,0 322.5,0 322.5,0 328.5,0 334.5,-6 334.5,-12 334.5,-12 334.5,-59 334.5,-59 334.5,-65 328.5,-71 322.5,-71\"/>\n<text text-anchor=\"start\" x=\"247\" y=\"-53.7\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">entropy = 0.0</text>\n<text text-anchor=\"start\" x=\"249.62\" y=\"-37.95\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">samples = 2</text>\n<text text-anchor=\"start\" x=\"247.75\" y=\"-22.2\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">value = [0, 2]</text>\n<text text-anchor=\"start\" x=\"255.62\" y=\"-6.45\" font-family=\"Helvetica,sans-Serif\" font-size=\"14.00\">class = 是</text>\n</g>\n<!-- 4&#45;&gt;6 -->\n<g id=\"edge6\" class=\"edge\">\n<title>4&#45;&gt;6</title>\n<path fill=\"none\" stroke=\"black\" d=\"M251.3,-106.7C255.51,-98.36 259.94,-89.59 264.19,-81.18\"/>\n<polygon fill=\"black\" stroke=\"black\" points=\"267.18,-83.01 268.57,-72.51 260.93,-79.86 267.18,-83.01\"/>\n</g>\n</g>\n</svg>\n",
      "text/plain": [
       "<graphviz.sources.Source at 0x2405d06fb00>"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 2
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 4.4",
   "id": "b7290d4a36e36625"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-04-22T09:13:56.884816Z",
     "start_time": "2025-04-22T09:13:29.073238Z"
    }
   },
   "cell_type": "code",
   "source": [
    "import pandas as pd\n",
    "from sklearn.tree import DecisionTreeClassifier, export_graphviz\n",
    "from sklearn.metrics import accuracy_score\n",
    "import graphviz\n",
    "\n",
    "# 构建数据集\n",
    "data = {\n",
    "    '编号': [1, 2, 3, 6, 7, 10, 14, 15, 16, 17, 4, 5, 8, 9, 11, 12, 13],\n",
    "    '色泽': ['青绿', '乌黑', '乌黑', '青绿', '乌黑', '青绿', '浅白', '乌黑', '浅白', '青绿', '青绿', '浅白', '乌黑', '乌黑', '浅白', '浅白', '青绿'],\n",
    "    '根蒂': ['蜷缩', '蜷缩', '蜷缩', '稍蜷', '稍蜷', '硬挺', '稍蜷', '稍蜷', '蜷缩', '蜷缩', '蜷缩', '蜷缩', '稍蜷', '稍蜷', '硬挺', '蜷缩', '稍蜷'],\n",
    "    '敲声': ['浊响', '沉闷', '浊响', '浊响', '浊响', '清脆', '沉闷', '浊响', '浊响', '沉闷', '沉闷', '浊响', '浊响', '沉闷', '清脆', '浊响', '浊响'],\n",
    "    '纹理': ['清晰', '清晰', '清晰', '清晰', '稍糊', '清晰', '稍糊', '清晰', '模糊', '稍糊', '清晰', '清晰', '清晰', '稍糊', '模糊', '模糊', '稍糊'],\n",
    "    '脐部': ['凹陷', '凹陷', '稍凹', '稍凹', '稍凹', '平坦', '凹陷', '稍凹', '平坦', '稍凹', '凹陷', '凹陷', '稍凹', '稍凹', '平坦', '平坦', '凹陷'],\n",
    "    '触感': ['硬滑', '硬滑', '软粘', '软粘', '软粘', '软粘', '硬滑', '软粘', '硬滑', '硬滑', '硬滑', '硬滑', '硬滑', '硬滑', '硬滑', '软粘', '硬滑'],\n",
    "    '好瓜': ['是', '是', '是', '是', '是', '否', '否', '否', '否', '否', '是', '是', '是', '否', '否', '否', '否']\n",
    "}\n",
    "df = pd.DataFrame(data)\n",
    "\n",
    "# 划分训练集和验证集\n",
    "train_indices = [1, 2, 3, 6, 7, 10, 14, 15, 16, 17]\n",
    "val_indices = [4, 5, 8, 9, 11, 12, 13]\n",
    "X_train = df[df['编号'].isin(train_indices)].drop(['编号', '好瓜'], axis=1)\n",
    "y_train = df[df['编号'].isin(train_indices)]['好瓜']\n",
    "X_val = df[df['编号'].isin(val_indices)].drop(['编号', '好瓜'], axis=1)\n",
    "y_val = df[df['编号'].isin(val_indices)]['好瓜']\n",
    "\n",
    "# 对特征进行编码\n",
    "from sklearn.preprocessing import LabelEncoder\n",
    "features = ['色泽', '根蒂', '敲声', '纹理', '脐部', '触感']\n",
    "label_encoders = {}\n",
    "for feature in features:\n",
    "    le = LabelEncoder()\n",
    "    X_train[feature] = le.fit_transform(X_train[feature])\n",
    "    X_val[feature] = le.transform(X_val[feature])\n",
    "    label_encoders[feature] = le\n",
    "y_train = LabelEncoder().fit_transform(y_train)\n",
    "y_val = LabelEncoder().fit_transform(y_val)\n",
    "\n",
    "\n",
    "# 未剪枝决策树\n",
    "unpruned_tree = DecisionTreeClassifier(criterion='gini', random_state=42)\n",
    "unpruned_tree.fit(X_train, y_train)\n",
    "y_pred_unpruned = unpruned_tree.predict(X_val)\n",
    "accuracy_unpruned = accuracy_score(y_val, y_pred_unpruned)\n",
    "print(f\"未剪枝决策树在验证集上的准确率: {accuracy_unpruned}\")\n",
    "\n",
    "# 预剪枝决策树\n",
    "pruned_tree_pre = DecisionTreeClassifier(criterion='gini', max_depth=3, random_state=42)\n",
    "pruned_tree_pre.fit(X_train, y_train)\n",
    "y_pred_pre = pruned_tree_pre.predict(X_val)\n",
    "accuracy_pre = accuracy_score(y_val, y_pred_pre)\n",
    "print(f\"预剪枝决策树在验证集上的准确率: {accuracy_pre}\")\n",
    "\n",
    "# 后剪枝决策树\n",
    "# 先构建一个过拟合的决策树\n",
    "overfit_tree = DecisionTreeClassifier(criterion='gini', random_state=42)\n",
    "overfit_tree.fit(X_train, y_train)\n",
    "\n",
    "def post_pruning(tree, X_val, y_val):\n",
    "    from sklearn.tree._tree import Tree\n",
    "    tree_ = tree.tree_\n",
    "    nodes_to_prune = []\n",
    "    for i in range(tree_.node_count):\n",
    "        if tree_.children_left[i] != -1 and tree_.children_right[i] != -1:  # 非叶子节点\n",
    "            # 备份左右子节点\n",
    "            left_child = tree_.children_left[i]\n",
    "            right_child = tree_.children_right[i]\n",
    "            # 计算剪枝前验证集准确率\n",
    "            y_pred_before = tree.predict(X_val)\n",
    "            accuracy_before = accuracy_score(y_val, y_pred_before)\n",
    "            # 进行剪枝操作（将左右子树设为叶子节点）\n",
    "            tree_.children_left[i] = -1\n",
    "            tree_.children_right[i] = -1\n",
    "            # 计算剪枝后验证集准确率\n",
    "            y_pred_after = tree.predict(X_val)\n",
    "            accuracy_after = accuracy_score(y_val, y_pred_after)\n",
    "            if accuracy_after >= accuracy_before:\n",
    "                nodes_to_prune.append(i)\n",
    "            else:\n",
    "                # 恢复节点\n",
    "                tree_.children_left[i] = left_child\n",
    "                tree_.children_right[i] = right_child\n",
    "    for node in nodes_to_prune:\n",
    "        tree_.children_left[node] = -1\n",
    "        tree_.children_right[node] = -1\n",
    "    return tree\n",
    "\n",
    "pruned_tree_post = post_pruning(overfit_tree, X_val, y_val)\n",
    "y_pred_post = pruned_tree_post.predict(X_val)\n",
    "accuracy_post = accuracy_score(y_val, y_pred_post)\n",
    "print(f\"后剪枝决策树在验证集上的准确率: {accuracy_post}\")\n",
    "\n",
    "# 可视化决策树\n",
    "def visualize_tree(tree, name):\n",
    "    dot_data = export_graphviz(tree, out_file=None,\n",
    "                               feature_names=features,\n",
    "                               class_names=['否', '是'],\n",
    "                               filled=True, rounded=True,\n",
    "                               special_characters=True)\n",
    "    graph = graphviz.Source(dot_data)\n",
    "    graph.render(name, format='png', cleanup=True)\n",
    "\n",
    "visualize_tree(unpruned_tree, 'unpruned_tree')\n",
    "visualize_tree(pruned_tree_pre, 'pre_pruned_tree')\n",
    "visualize_tree(pruned_tree_post, 'post_pruned_tree')"
   ],
   "id": "20f2372fcf1d1fa0",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "未剪枝决策树在验证集上的准确率: 0.42857142857142855\n",
      "预剪枝决策树在验证集上的准确率: 0.42857142857142855\n",
      "后剪枝决策树在验证集上的准确率: 0.5714285714285714\n"
     ]
    }
   ],
   "execution_count": 3
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 4.5",
   "id": "a938ce397bfa2183"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-04-23T07:02:44.301323Z",
     "start_time": "2025-04-23T07:02:41.444489Z"
    }
   },
   "cell_type": "code",
   "source": [
    "import numpy as np\n",
    "from sklearn.datasets import load_iris\n",
    "from sklearn.linear_model import LogisticRegression\n",
    "from sklearn.tree import DecisionTreeClassifier, export_text\n",
    "from sklearn.preprocessing import LabelEncoder\n",
    "\n",
    "# 假设西瓜数据集存储在一个二维数组data中，最后一列为标签\n",
    "# 这里先模拟生成数据，实际使用时请替换为你的真实数据\n",
    "# 假设数据集中前面几列是特征，最后一列是好瓜（是/否）的标签\n",
    "# 这里先不考虑特征名称，按顺序处理\n",
    "# 先将数据转化为numpy数组\n",
    "data = np.array([\n",
    "    ['青绿', '蜷缩', '浊响', '清晰', '凹陷', '硬滑', 0.697, 0.460, '是'],\n",
    "    ['乌黑', '蜷缩', '沉闷', '清晰', '凹陷', '硬滑', 0.774, 0.376, '是'],\n",
    "    ['乌黑', '蜷缩', '浊响', '清晰', '凹陷', '硬滑', 0.634, 0.264, '是'],\n",
    "    ['青绿', '蜷缩', '沉闷', '清晰', '凹陷', '硬滑', 0.608, 0.318, '是'],\n",
    "    ['浅白', '蜷缩', '浊响', '清晰', '凹陷', '硬滑', 0.556, 0.215, '是'],\n",
    "    ['青绿', '稍蜷', '浊响', '清晰', '稍凹', '软粘', 0.403, 0.237, '是'],\n",
    "    ['乌黑', '稍蜷', '浊响', '稍糊', '稍凹', '软粘', 0.481, 0.149, '是'],\n",
    "    ['乌黑', '稍蜷', '浊响', '清晰', '稍凹', '硬滑', 0.437, 0.211, '是'],\n",
    "    ['乌黑', '稍蜷', '沉闷', '稍糊', '稍凹', '硬滑', 0.666, 0.091, '否'],\n",
    "    ['青绿', '硬挺', '清脆', '清晰', '稍凹', '硬滑', 0.243, 0.267, '否'],\n",
    "    ['浅白', '硬挺', '清脆', '模糊', '平坦', '软粘', 0.245, 0.057, '否'],\n",
    "    ['浅白', '蜷缩', '浊响', '模糊', '平坦', '硬滑', 0.343, 0.099, '否'],\n",
    "    ['青绿', '稍蜷', '浊响', '稍糊', '凹陷', '硬滑', 0.639, 0.161, '否'],\n",
    "    ['浅白', '稍蜷', '沉闷', '稍糊', '凹陷', '硬滑', 0.657, 0.198, '否'],\n",
    "    ['乌黑', '稍蜷', '浊响', '清晰', '稍凹', '软粘', 0.360, 0.370, '否'],\n",
    "    ['浅白', '蜷缩', '浊响', '模糊', '稍凹', '硬滑', 0.593, 0.042, '否'],\n",
    "    ['青绿', '蜷缩', '沉闷', '稍糊', '稍凹', '硬滑', 0.719, 0.103, '否']\n",
    "])\n",
    "\n",
    "# 对类别型特征进行编码\n",
    "label_encoders = []\n",
    "for i in range(data.shape[1] - 1):\n",
    "    le = LabelEncoder()\n",
    "    data[:, i] = le.fit_transform(data[:, i])\n",
    "    label_encoders.append(le)\n",
    "\n",
    "# 分离特征和标签\n",
    "X = data[:, :-1].astype(float)\n",
    "y = data[:, -1]\n",
    "le_y = LabelEncoder()\n",
    "y = le_y.fit_transform(y)\n",
    "\n",
    "# 使用对率回归进行特征选择（这里简单示例，实际可能需要更复杂策略）\n",
    "logreg = LogisticRegression()\n",
    "logreg.fit(X, y)\n",
    "feature_importance = np.abs(logreg.coef_[0])\n",
    "selected_features = feature_importance > 0.01  # 简单阈值选择，可调整\n",
    "X_selected = X[:, selected_features]\n",
    "\n",
    "# 构建决策树\n",
    "dtc = DecisionTreeClassifier()\n",
    "dtc.fit(X_selected, y)\n",
    "\n",
    "# 输出决策树文本表示\n",
    "tree_rules = export_text(dtc, feature_names=[f'feature_{i}' for i in range(X_selected.shape[1])])\n",
    "print(tree_rules)"
   ],
   "id": "db048345a376c180",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "|--- feature_7 <= 7.50\n",
      "|   |--- feature_0 <= 0.50\n",
      "|   |   |--- feature_5 <= 0.50\n",
      "|   |   |   |--- class: 0\n",
      "|   |   |--- feature_5 >  0.50\n",
      "|   |   |   |--- class: 1\n",
      "|   |--- feature_0 >  0.50\n",
      "|   |   |--- class: 0\n",
      "|--- feature_7 >  7.50\n",
      "|   |--- feature_6 <= 3.50\n",
      "|   |   |--- class: 0\n",
      "|   |--- feature_6 >  3.50\n",
      "|   |   |--- class: 1\n",
      "\n"
     ]
    }
   ],
   "execution_count": 1
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 4.6",
   "id": "a0d4d23d13a8c21c"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-04-23T07:23:13.251166Z",
     "start_time": "2025-04-23T07:23:06.353261Z"
    }
   },
   "cell_type": "code",
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "from sklearn.datasets import load_iris, load_breast_cancer, load_wine\n",
    "from sklearn.model_selection import train_test_split\n",
    "from sklearn.preprocessing import LabelEncoder\n",
    "from sklearn.tree import DecisionTreeClassifier\n",
    "from sklearn.metrics import accuracy_score, recall_score, f1_score\n",
    "from scipy.stats import friedmanchisquare\n",
    "import scikit_posthocs as sp\n",
    "import requests\n",
    "from io import StringIO\n",
    "\n",
    "\n",
    "# 加载UCI玻璃识别数据集\n",
    "def load_glass_data():\n",
    "    url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/glass/glass.data'\n",
    "    response = requests.get(url)\n",
    "    if response.status_code == 200:\n",
    "        data = response.text\n",
    "        df = pd.read_csv(StringIO(data), header=None)\n",
    "        X = df.iloc[:, 1:-1].values\n",
    "        y = df.iloc[:, -1].values\n",
    "        return X, y\n",
    "    else:\n",
    "        print(\"Failed to download the dataset.\")\n",
    "        return None, None\n",
    "\n",
    "\n",
    "# 加载UCI数据集\n",
    "datasets = {\n",
    "    'Iris': load_iris(),\n",
    "    'Breast Cancer': load_breast_cancer(),\n",
    "    'Wine': load_wine(),\n",
    "    'Glass': {'data': load_glass_data()[0], 'target': load_glass_data()[1]}\n",
    "}\n",
    "\n",
    "results = []\n",
    "\n",
    "for dataset_name, dataset in datasets.items():\n",
    "    X = dataset['data']\n",
    "    y = dataset['target']\n",
    "\n",
    "    # 数据划分\n",
    "    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)\n",
    "\n",
    "    # 标签编码（如果有分类特征）\n",
    "    if dataset_name == 'Glass':\n",
    "        le = LabelEncoder()\n",
    "        y_train = le.fit_transform(y_train)\n",
    "        y_test = le.transform(y_test)\n",
    "\n",
    "    # 未剪枝决策树\n",
    "    unpruned_tree = DecisionTreeClassifier(criterion='gini', random_state=42)\n",
    "    unpruned_tree.fit(X_train, y_train)\n",
    "    y_pred_unpruned = unpruned_tree.predict(X_test)\n",
    "    unpruned_accuracy = accuracy_score(y_test, y_pred_unpruned)\n",
    "    unpruned_recall = recall_score(y_test, y_pred_unpruned, average='weighted')\n",
    "    unpruned_f1 = f1_score(y_test, y_pred_unpruned, average='weighted')\n",
    "\n",
    "    # 预剪枝决策树\n",
    "    pruned_tree_pre = DecisionTreeClassifier(criterion='gini', max_depth=3, random_state=42)\n",
    "    pruned_tree_pre.fit(X_train, y_train)\n",
    "    y_pred_pre = pruned_tree_pre.predict(X_test)\n",
    "    pre_accuracy = accuracy_score(y_test, y_pred_pre)\n",
    "    pre_recall = recall_score(y_test, y_pred_pre, average='weighted')\n",
    "    pre_f1 = f1_score(y_test, y_pred_pre, average='weighted')\n",
    "\n",
    "    # 后剪枝决策树\n",
    "    overfit_tree = DecisionTreeClassifier(criterion='gini', random_state=42)\n",
    "    overfit_tree.fit(X_train, y_train)\n",
    "\n",
    "\n",
    "    def post_pruning(tree, X_val, y_val):\n",
    "        from sklearn.tree._tree import Tree\n",
    "        tree_ = tree.tree_\n",
    "        nodes_to_prune = []\n",
    "        for i in range(tree_.node_count):\n",
    "            if tree_.children_left[i] != -1 and tree_.children_right[i] != -1:  # 非叶子节点\n",
    "                # 备份左右子节点\n",
    "                left_child = tree_.children_left[i]\n",
    "                right_child = tree_.children_right[i]\n",
    "                # 计算剪枝前验证集准确率\n",
    "                y_pred_before = tree.predict(X_val)\n",
    "                accuracy_before = accuracy_score(y_val, y_pred_before)\n",
    "                # 进行剪枝操作（将左右子树设为叶子节点）\n",
    "                tree_.children_left[i] = -1\n",
    "                tree_.children_right[i] = -1\n",
    "                # 计算剪枝后验证集准确率\n",
    "                y_pred_after = tree.predict(X_val)\n",
    "                accuracy_after = accuracy_score(y_val, y_pred_after)\n",
    "                if accuracy_after >= accuracy_before:\n",
    "                    nodes_to_prune.append(i)\n",
    "                else:\n",
    "                    # 恢复节点\n",
    "                    tree_.children_left[i] = left_child\n",
    "                    tree_.children_right[i] = right_child\n",
    "        for node in nodes_to_prune:\n",
    "            tree_.children_left[node] = -1\n",
    "            tree_.children_right[node] = -1\n",
    "        return tree\n",
    "\n",
    "\n",
    "    pruned_tree_post = post_pruning(overfit_tree, X_test, y_test)\n",
    "    y_pred_post = pruned_tree_post.predict(X_test)\n",
    "    post_accuracy = accuracy_score(y_test, y_pred_post)\n",
    "    post_recall = recall_score(y_test, y_pred_post, average='weighted')\n",
    "    post_f1 = f1_score(y_test, y_pred_post, average='weighted')\n",
    "\n",
    "    results.append({\n",
    "        'Dataset': dataset_name,\n",
    "        'Model': 'Unpruned Tree',\n",
    "        'Accuracy': unpruned_accuracy,\n",
    "        'Recall': unpruned_recall,\n",
    "        'F1-Score': unpruned_f1\n",
    "    })\n",
    "    results.append({\n",
    "        'Dataset': dataset_name,\n",
    "        'Model': 'Pre-Pruned Tree',\n",
    "        'Accuracy': pre_accuracy,\n",
    "        'Recall': pre_recall,\n",
    "        'F1-Score': pre_f1\n",
    "    })\n",
    "    results.append({\n",
    "        'Dataset': dataset_name,\n",
    "        'Model': 'Post-Pruned Tree',\n",
    "        'Accuracy': post_accuracy,\n",
    "        'Recall': post_recall,\n",
    "        'F1-Score': post_f1\n",
    "    })\n",
    "\n",
    "# 统计显著性检验\n",
    "df = pd.DataFrame(results)\n",
    "accuracy_scores = df.pivot(index='Dataset', columns='Model', values='Accuracy')\n",
    "recall_scores = df.pivot(index='Dataset', columns='Model', values='Recall')\n",
    "f1_scores = df.pivot(index='Dataset', columns='Model', values='F1-Score')\n",
    "\n",
    "# Friedman检验\n",
    "friedman_accuracy = friedmanchisquare(accuracy_scores['Unpruned Tree'], accuracy_scores['Pre-Pruned Tree'],\n",
    "                                      accuracy_scores['Post-Pruned Tree'])\n",
    "friedman_recall = friedmanchisquare(recall_scores['Unpruned Tree'], recall_scores['Pre-Pruned Tree'],\n",
    "                                    recall_scores['Post-Pruned Tree'])\n",
    "friedman_f1 = friedmanchisquare(f1_scores['Unpruned Tree'], f1_scores['Pre-Pruned Tree'],\n",
    "                                f1_scores['Post-Pruned Tree'])\n",
    "\n",
    "# Nemenyi后续检验\n",
    "nemenyi_accuracy = sp.posthoc_nemenyi_friedman(accuracy_scores)\n",
    "nemenyi_recall = sp.posthoc_nemenyi_friedman(recall_scores)\n",
    "nemenyi_f1 = sp.posthoc_nemenyi_friedman(f1_scores)\n",
    "\n",
    "print(\"Friedman检验（准确率）:\", friedman_accuracy)\n",
    "print(\"Friedman检验（召回率）:\", friedman_recall)\n",
    "print(\"Friedman检验（F1值）:\", friedman_f1)\n",
    "print(\"\\nNemenyi后续检验（准确率）:\\n\", nemenyi_accuracy)\n",
    "print(\"\\nNemenyi后续检验（召回率）:\\n\", nemenyi_recall)\n",
    "print(\"\\nNemenyi后续检验（F1值）:\\n\", nemenyi_f1)"
   ],
   "id": "41ac2f5f86d22d70",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Friedman检验（准确率）: FriedmanchisquareResult(statistic=3.7142857142857144, pvalue=0.15611804531597104)\n",
      "Friedman检验（召回率）: FriedmanchisquareResult(statistic=3.7142857142857144, pvalue=0.15611804531597104)\n",
      "Friedman检验（F1值）: FriedmanchisquareResult(statistic=3.2, pvalue=0.2018965179946554)\n",
      "\n",
      "Nemenyi后续检验（准确率）:\n",
      "                   Post-Pruned Tree  Pre-Pruned Tree  Unpruned Tree\n",
      "Post-Pruned Tree          1.000000         0.933422       0.650495\n",
      "Pre-Pruned Tree           0.933422         1.000000       0.431045\n",
      "Unpruned Tree             0.650495         0.431045       1.000000\n",
      "\n",
      "Nemenyi后续检验（召回率）:\n",
      "                   Post-Pruned Tree  Pre-Pruned Tree  Unpruned Tree\n",
      "Post-Pruned Tree          1.000000         0.933422       0.650495\n",
      "Pre-Pruned Tree           0.933422         1.000000       0.431045\n",
      "Unpruned Tree             0.650495         0.431045       1.000000\n",
      "\n",
      "Nemenyi后续检验（F1值）:\n",
      "                   Post-Pruned Tree  Pre-Pruned Tree  Unpruned Tree\n",
      "Post-Pruned Tree          1.000000         0.759287       0.759287\n",
      "Pre-Pruned Tree           0.759287         1.000000       0.333499\n",
      "Unpruned Tree             0.759287         0.333499       1.000000\n"
     ]
    }
   ],
   "execution_count": 4
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": "",
   "id": "9f8e28bf7c2aaa65"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
